import pyglet
from pyglet.gl import *
from pyglet import clock
from pyglet import image
from pyglet.window import mouse, key
from collections import defaultdict
import math

from Vector import Vector3, Vector2
from Camera import Camera
from Block import MinecraftLevel, MinecraftBlock, Chunk, memoize

ChunkXSize = 16
ChunkYSize = 128
ChunkZSize = 16

m = 1.0 / 16.0

fill = True

norock = True
tunnel = False

tt = image.load('terrain.png')
tt_t = tt.get_texture()
glEnable(tt_t.target)
glBindTexture(tt_t.target, tt_t.id)

@memoize
def get_cube(sides, top):
    #print "Making new list for", sides, top
    dl = glGenLists(1)
    glNewList(dl, GL_COMPILE)
    glBegin(GL_QUADS)
    #// Front Face
    glNormal3f( 0.0, 0.0, 0.5)
    glTexCoord2f(sides[0] * m, sides[1] * m)
    glVertex3f(-0.5, 0,  0.5)
    glTexCoord2f((sides[0]+1) * m, sides[1]* m)
    glVertex3f( 0.5, 0,  0.5)
    glTexCoord2f((sides[0]+1) * m, (sides[1]+1)* m)
    glVertex3f( 0.5,  1,  0.5)
    glTexCoord2f(sides[0] * m, (sides[1]+1)* m)
    glVertex3f(-0.5,  1,  0.5)
    #// Back Face
    glNormal3f( 0.0, 0.0,-0.5)
    glTexCoord2f((sides[0]+1) * m, sides[1]* m)
    glVertex3f(-0.5, 0, -0.5)
    glTexCoord2f((sides[0]+1) * m, (sides[1]+1)* m)
    glVertex3f(-0.5,  1, -0.5)
    glTexCoord2f(sides[0] * m, (sides[1]+1)* m)
    glVertex3f( 0.5,  1, -0.5)
    glTexCoord2f(sides[0] * m, sides[1]* m)
    glVertex3f( 0.5, 0, -0.5)
    #// Top Face
    glNormal3f( 0.0, 0.5, 0.0)
    glTexCoord2f(top[0] * m, (top[1]+1)* m)
    glVertex3f(-0.5,  1, -0.5)
    glTexCoord2f(top[0] * m, top[1]* m)
    glVertex3f(-0.5,  1,  0.5)
    glTexCoord2f((top[0]+1) * m, top[1]* m)
    glVertex3f( 0.5,  1,  0.5)
    glTexCoord2f((top[0]+1) * m, (top[1]+1)* m)
    glVertex3f( 0.5,  1, -0.5)
    #// Bottom Face
    glNormal3f( 0.0,0, 0.0)
    glTexCoord2f((sides[0]+1) * m, (sides[1]+1)* m)
    glVertex3f(-0.5, 0, -0.5)
    glTexCoord2f(sides[0] * m, (sides[1]+1)* m)
    glVertex3f( 0.5, 0, -0.5)
    glTexCoord2f(sides[0] * m, sides[1]* m)
    glVertex3f( 0.5, 0,  0.5)
    glTexCoord2f((sides[0]+1) * m, sides[1]* m)
    glVertex3f(-0.5, 0,  0.5)
    #// Right Face
    glNormal3f( 0.5, 0.0, 0.0)
    glTexCoord2f((sides[0]+1) * m, sides[1]* m)
    glVertex3f( 0.5, 0, -0.5)
    glTexCoord2f((sides[0]+1) * m, (sides[1]+1)* m)
    glVertex3f( 0.5,  1, -0.5)
    glTexCoord2f(sides[0] * m, (sides[1]+1)* m)
    glVertex3f( 0.5,  1,  0.5)
    glTexCoord2f(sides[0] * m, sides[1]* m)
    glVertex3f( 0.5, 0,  0.5)
    #// Left Face
    glNormal3f(-0.5, 0.0, 0.0)
    glTexCoord2f(sides[0] * m, sides[1]* m)
    glVertex3f(-0.5, 0, -0.5)
    glTexCoord2f((sides[0]+1) * m, sides[1]* m)
    glVertex3f(-0.5, 0,  0.5)
    glTexCoord2f((sides[0]+1) * m, (sides[1]+1)* m)
    glVertex3f(-0.5,  1,  0.5)
    glTexCoord2f(sides[0] * m, (sides[1]+1)* m)
    glVertex3f(-0.5,  1, -0.5)

    glEnd()
    glEndList()
    return dl

xz = []
for x in xrange(ChunkXSize):
    for z in xrange(ChunkZSize):
        xz.append((x, z))

colors = [(x / 15.0, 0.0, 1.0 - (x / 15.0)) for x in range(16)]

class RenderableChunk:
    def __init__(self, base, chunk):
        self.base = base
        self.chunk = chunk
        self.blocks = defaultdict(list)
        self.progress = 75 # formerly ChunkYSize
        self.prime = [[ False for i in range(16)] for j in range(16)]

    def prep(self):
        if not self.chunk:
            return
        # This is so slow.. please optimize me.
        self.progress -= 1
        y = self.progress
        look = self.chunk.Lookup
        for x, z in xz:
            b = look(x, y, z)
            if b.id:
                self.prime[x][z] = True
            elif b.id == 0:
                if not self.prime[x][z]:
                    continue
                else:
                    #b.color = (b.blockLight / 16.0, 0.0, 1.0 - (b.blockLight / 16.0))
                    b.color = colors[b.blockLight]
            b.pos = x, y, z
            self.blocks[b.id].append(b)

    def draw(self):
        if self.progress:
            self.prep()
        global norock, tunnel
        glEnable(tt_t.target)
        glBindTexture(tt_t.target, tt_t.id)
        glPushMatrix()
        glTranslatef(self.base[0] * ChunkXSize, -70, self.base[1] * ChunkZSize)
        glColor3f(1.0, 1.0, 1.0)
        n = MinecraftBlock.name_to_id
        tunnel_items = [n['Space'], n['Gold Ore'], n['Iron Ore'], n['Coal Ore'], n['Torch'], n['Mob Spawner']
                        , n['Diamond Ore'], n['Redstone Ore'], n['Glowing Redstone Ore']]
        norock_items = [n['Stone'], n['Dirt'], n['Gravel']]
        for id, bs in self.blocks.iteritems():
            if not tunnel and not id:
                continue
            if tunnel and id not in tunnel_items:
                continue
            if norock and id in norock_items:
                continue
            sca = MinecraftBlock.types[id].sca
            cu = get_cube(*MinecraftBlock.types[id].coor)
            glColor3f(1.0, 1.0, 1.0)
            for b in bs:
                if not id:
                    glColor3f(*b.color)
                glPushMatrix()
                glTranslatef(*b.pos)
                if sca:
                    glScalef(*sca)
                glCallList(cu)
                glPopMatrix()
        glPopMatrix()

class ChunkWrapper:
    chunks = {}
    current_loading = None
    def __init__(self, ml, index):
        self.ml = ml
        self.index = index
        self.chunk = None
        if index in ChunkWrapper.chunks:
            self.chunk = ChunkWrapper.chunks[index]

    @memoize
    def GetChunk(self, index):
        print "Building chunk", index
        return RenderableChunk(index, self.ml.LoadChunk(*index))

    def draw(self):
        if ChunkWrapper.current_loading and ChunkWrapper.current_loading.progress == 0:
            ChunkWrapper.current_loading = None
        if not self.chunk:
            if ChunkWrapper.current_loading == None:
                self.chunk = self.GetChunk(self.index)
                ChunkWrapper.chunks[self.index] = self.chunk
                ChunkWrapper.current_loading = self.chunk
            else:
                return
        self.chunk.draw()

class Chunker:
    def __init__(self, ml):
        self.ml = ml
        self.radius = 2
        self.ordinal = None
        self.vchunks = []
        self.vchunkarr = []
        self.move(0, 0)

    def draw(self):
        for dist, chunk in self.vchunks:
            chunk.draw()

    @memoize
    def getWrapper(self, index):
        return ChunkWrapper(self.ml, index)

    def move(self, x, y):
        no = int(x / 16), int(y / 16)
        if no == self.ordinal:
            return
        self.ordinal = no

        ChunkWrapper.current_loading = None

        self.vchunks = []
        self.vchunkarr = []
        for xd in range(self.ordinal[0] - self.radius, self.ordinal[0] + self.radius):
            nr = []
            for yd in range(self.ordinal[1] - self.radius, self.ordinal[1] + self.radius):
                p = int(xd), int(yd)
                w = self.getWrapper(p)
                self.vchunks.append((Vector2(xd, yd).distance_squared(Vector2(*no)), w))
                nr.append(w)
            nr.reverse()
            self.vchunkarr.append(nr)
        self.vchunks.sort()
        #self.vchunkarr.reverse()

gl1df = lambda data: (GLfloat * len(data))(*data)

twopi = 2 * 3.14159
fract = twopi / 64.0

def draw_progress_circle(progress):
    prog = int(progress * 64)
    glDisable(GL_CULL_FACE)
    glBegin(GL_TRIANGLE_FAN)
    if prog:
        glColor3f(1.0 - progress, progress, 0.0)
    else:
        prog = 64
        glColor3f(0.0, 0.0, 0.0)
    glVertex2f(0.0, 0.0)
    glVertex2f(0.0, 10.0)
    for i in range(prog + 1):
        glVertex2f(math.sin(fract * i), math.cos(fract * i))
    glEnd()
    glEnable(GL_CULL_FACE)

class RenderWindow(pyglet.window.Window):
    def __init__(self, width, height, caption):
        pyglet.window.Window.__init__(self, width, height, caption=caption)
        self.width = width
        self.height = height
        glEnable(GL_CULL_FACE)
        glDisable(GL_BLEND)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_ALPHA_TEST)
        glAlphaFunc(GL_GREATER, 0.95)
        #glBlendFunc(GL_SRC_ALPHA, GL_ONE)

        #glEnable(GL_LIGHTING)
        glLightfv(GL_LIGHT1, GL_AMBIENT, gl1df([0.0, 0.2, 0.1, 1.0]))
        glLightfv(GL_LIGHT1, GL_DIFFUSE, gl1df([1.0, 1.0, 1.0, 1.0]))
        glLightfv(GL_LIGHT1, GL_POSITION, gl1df([100.0, 10.0, 100.0, 1.0]))
        #glEnable(GL_LIGHT1)

    def enter_2d(self):
        w, h = self.width, self.height
        glViewport(0, 0, w, h)
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        size = max(w, h) / 2.0
        aspect = float(w) / float(h)
        if w <= h:
            aspect = float(h) / float(w)
            glOrtho(-size, size, -size*aspect, size*aspect, -100000.0, 100000.0)
        else:
            glOrtho(-size*aspect, size*aspect, -size, size, -100000.0, 100000.0)
        glScaled(aspect, aspect, 1.0)
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix();
        glLoadIdentity()
        # Sets the origin at the bottom left corner
        glTranslatef(-(w / 2), -(h / 2), 0)

    def exit_any_d(self):
        glMatrixMode(GL_PROJECTION)
        glPopMatrix()

        glMatrixMode(GL_MODELVIEW);
        glPopMatrix()

    def enter_3d(self):
        glViewport(0, 0, self.width, self.height)
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        gluPerspective(45, self.width / float(self.height), .1, 1000)
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()

    def on_resize(self, width, height):
        self.enter_3d()
        return pyglet.event.EVENT_HANDLED

    def on_draw(self):
        global camera, chunker
        if not self.height:
            return
        glClearColor(0.6, 0.6, 0.6, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        self.enter_3d()
        camera.use()
        chunker.draw()
        self.exit_any_d()

        self.enter_2d()
        glDisable(GL_TEXTURE_2D)
        for i, row in enumerate(chunker.vchunkarr):
            i += 1
            for j, chunk in enumerate(row):
                j += 1
                glPushMatrix()
                glTranslatef(i * 20, j * 20, 0)
                glScalef(7.0, 7.0, 1.0)
                draw_progress_circle((1.0 - min(64, chunk.chunk.progress) / 64.0) if chunk.chunk else 0.0)
                glPopMatrix()
        self.exit_any_d()

    def on_mouse_press(self, x, y, button, modifiers):
        pass
        #if button == mouse.LEFT:
            #grow()
        #if button == mouse.RIGHT:
            #gen()
 
    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        global camera
        camera.turn(dx * -0.01, dy * 0.01)

    def on_key_press(self, symbol, modifiers):
        global camera, fill, chunker, norock, tunnel
        if symbol == key.W:
            camera.move(4, 0.0)
        if symbol == key.S:
            camera.move(-4, 0.0)
        if symbol == key.A:
            camera.move(0.0, -4)
        if symbol == key.D:
            camera.move(0.0, 4)
        if symbol == key.R:
            norock = not norock
        if symbol == key.T:
            tunnel = not tunnel
        if symbol == key.P:
            fill = not fill
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL if fill else GL_LINE)
        if symbol == key.U:
            pyglet.image.get_buffer_manager().get_color_buffer().get_image_data().save("shot.png")
        chunker.move(camera.pos.x, camera.pos.z)

def body(dt):
    pass

MinecraftBlock.setup_types()

import os, platform
if platform.system() == 'Windows':
    lp = "%s\.minecraft\saves\World" % os.environ['APPDATA']
elif platform.system() == 'Darwin': #Mac OSX
    lp = "%s/Library/Application Support/minecraft/saves/World" % os.environ['HOME']
else: #Linux (I think, untested)
    #lp = "%s/.minecraft/saves/World" % os.environ['HOME']
    lp = "World"
while True:
    n = input("Please choose a world: (1-5)")
    np = "%s%d" % (lp, n)
    if os.path.exists(np):
        lp = np
        break
    else:
        print "World ", n, "doesn't exist (is %s your minecraft path?)\n\n" % np

camera = Camera(Vector3(-9 * 16, 10, -1 * 16), Vector3(-25, 0, -25))

chunker = Chunker(MinecraftLevel(lp))
chunker.move(camera.pos.x, camera.pos.z)

window = RenderWindow(900, 600, "Dowsing Rod!")
window.clear()
window.flip()
window.set_visible(True)

clock.schedule_interval(body, 1.0/60.0)
#import profile
#profile.run('pyglet.app.run()')
pyglet.app.run()
